﻿using System.IO.Compression;
using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.IO;
using System.Net;
using System.Text;
using UMC.Net;

namespace UMC.ITME
{

    class HttpMimeResponse
    {
        protected static readonly byte[] ChunkedEnd = Encoding.ASCII.GetBytes("0\r\n\r\n");
        public int StatusCode
        {
            get;
            set;
        }
        public StringValue ContentType
        {
            get;
            set;
        }
        public long? ContentLength { set; get; }

        HttpMimeRequest _req;
        NetWriteData _writeData;

        HttpLoger netLoger;
        public HttpMimeResponse(HttpMimeRequest request, NetWriteData writeData, HttpLoger loger)
        {
            this.netLoger = loger;
            this._req = request;
            this.StatusCode = 200;
            this._writeData = writeData;
            this.bodyStream = new BodyStream(this);
        }
        List<HeadValue> headers = new List<HeadValue>();
        public void AddHeader(HeadValue head)
        {
            if (this.lengthWrite == 0)
            {
                headers.Add(head);
            }
        }

        public Action Headed;

        public void AddHeader(string name, string value)
        {
            AddHeader(new HeadValue(name, value));
        }

        public void Redirect(StringValue url)
        {
            if (this.lengthWrite == 0)
            {
                this.StatusCode = 302;
                this.AddHeader(new HeadValue(StringValue.Location, url));
                this.ContentLength = null;
                this.isChunked = false;
                this.WriteHeader();
                this.lengthWrite = -1;

            }

        }
        BodyStream bodyStream;
        public System.IO.Stream OutputStream
        {
            get
            {
                return bodyStream;
            }
        }
        bool isChunked;
        void WriteHeader()
        {
            var header = new UMC.Net.TextWriter(this._writeData);
            netLoger.Write(HttpLogerType.Status, this.StatusCode);
            try
            {
                // header.Write(Mime.HTTP11, 0, 8);
                // header.Write(Mime.HeaderSplit, 1, 1);
                // header.Write($"{this.StatusCode}");
                // header.Write($"{HttpStatusDescription.Get(this.StatusCode)}\r\n");

                header.Write($"HTTP/1.1 {StatusCode} {HttpStatusDescription.Get(StatusCode)}\r\n");
                foreach (var h in this.headers)
                {

                    header.Write(h.Name.Span);
                    header.Write(Mime.HeaderSplit);
                    header.Write(h.Value.Span);
                    header.Write(Mime.RowEnd);
                }
                if (this.ContentType?.IsEmpty == false)
                {
                    header.Write(StringValue.ContentType.Span);
                    header.Write(Mime.HeaderSplit);
                    header.Write(this.ContentType.Span);
                    header.Write(Mime.RowEnd);
                }

                if (this.ContentLength.HasValue && this.ContentLength > 0)
                {

                    header.Write(StringValue.ContentLength.Span);
                    header.Write(Mime.HeaderSplit);
                    header.Write($"{this.ContentLength}\r\n");
                }
                else if (isChunked)
                {
                    header.Write(StringValue.TransferEncoding.Span);
                    header.Write(Mime.HeaderSplit);
                    header.Write("chunked\r\n");
                }
                else
                {
                    header.Write(StringValue.ContentLength.Span);
                    header.Write(Mime.HeaderSplit);
                    header.Write("0\r\n");
                }

                if (_IsClose || _req.IsHttpFormatError)
                {
                    header.Write(StringValue.Connection.Span);
                    header.Write(Mime.HeaderSplit);
                    header.Write("close\r\n");
                }
                else
                {
                    header.Write(StringValue.KeepAlive.Span);
                    header.Write(Mime.HeaderSplit);
                    header.Write("timeout=20\r\n");

                    header.Write(StringValue.Connection.Span);
                    header.Write(Mime.HeaderSplit);
                    header.Write("keep-alive\r\n");
                }
                header.Write("Server: ITME\r\n\r\n");
                Headed?.Invoke();
                _req.StatusCode(this.StatusCode);

            }
            catch (Exception ex)
            {
                this.netLoger.Error().Write(ex.ToString()).End();

            }
            finally
            {
                header.Flush();
                header.Dispose();
            }
        }
        bool _IsClose;
        public void OutputError(Exception ex)
        {
            if (lengthWrite == 0)
            {
                var errStr = ex?.ToString() ?? "Unknown Exception";
                var sbytes = System.Buffers.ArrayPool<byte>.Shared.Rent(errStr.Length * 2);
                try
                {
                    this.StatusCode = 500;
                    _IsClose = true;

                    netLoger.Error().Write(errStr, out var stringValue).End();

                    this.ContentType = StringValue.ContentTypeText;

                    this.ContentLength = stringValue.Length;
                    this.isChunked = false;

                    stringValue.Output(bodyStream.Write);
                }
                finally
                {
                    System.Buffers.ArrayPool<byte>.Shared.Return(sbytes);
                }

            }
            else if (this.isChunked)
            {
                this._writeData(ChunkedEnd, 0, ChunkedEnd.Length);
            }
        }
        public bool OutputFinish()
        {
            try
            {
                if (lengthWrite == -1)
                {
                    return true;
                }
                else if (lengthWrite == 0)
                {
                    if (this.ContentLength > 0)
                    {
                        _IsClose = true;
                        this.ContentLength = 0;
                        this.isChunked = false;
                        WriteHeader();
                        lengthWrite = -1;
                        return false;
                    }
                    else
                    {
                        this.ContentLength = 0;
                        this.isChunked = false;
                        WriteHeader();
                        lengthWrite = -1;
                        return true;
                    }

                }
                else if (this.isChunked)
                {
                    this.bodyStream.Flush();

                    _writeData(ChunkedEnd, 0, ChunkedEnd.Length);

                    return true;
                }
                else
                {
                    return lengthWrite == this.ContentLength;
                }
            }
            finally
            {
                headers.Clear();
                this.bodyStream.Dispose();
            }

        }
        long lengthWrite = 0;
        class BodyStream : System.IO.Stream
        {
            HttpMimeResponse response;
            public BodyStream(HttpMimeResponse response)
            {
                this.response = response;
            }
            public override bool CanRead => false;

            public override bool CanSeek => false;


            public override bool CanWrite => true;

            public override long Length => throw new NotSupportedException();

            public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }

            public override void Flush()
            {

            }


            public override int Read(byte[] buffer, int offset, int count)
            {
                throw new NotSupportedException();
            }

            public override long Seek(long offset, SeekOrigin origin)
            {
                throw new NotSupportedException();
            }
            public override void SetLength(long value)
            {
                throw new NotSupportedException();
            }

            public override void Write(byte[] buffer, int offset, int count)
            {
                switch (response.lengthWrite)
                {
                    case -1:

                        return;
                    case 0:
                        response.isChunked = response.ContentLength.HasValue == false || response.ContentLength <= 0;
                        response.WriteHeader();

                        break;
                }
                response.lengthWrite += count;
                if (response.ContentLength > 0)
                {
                    if (response.ContentLength < response.lengthWrite)
                    {
                        var size = response.ContentLength.Value - (response.lengthWrite - count);
                        if (size > 0)
                        {
                            response._writeData(buffer, offset, (int)size);
                        }
                        response._IsClose = true;
                    }
                    else
                    {
                        response._writeData(buffer, offset, count);
                    }
                }
                else if (count > 0)
                {
                    var str = $"{count:X}\r\n";
                    var bytes = System.Buffers.ArrayPool<byte>.Shared.Rent(str.Length);
                    try
                    {
                        response._writeData(bytes, 0, Encoding.ASCII.GetBytes(str, bytes));
                        response._writeData(buffer, offset, count);
                        response._writeData(UMC.Net.Mime.HeaderEnd, 0, 2);
                    }
                    finally
                    {
                        System.Buffers.ArrayPool<byte>.Shared.Return(bytes);
                    }
                }
            }
        }
    }


}